<?php
// -----------------------------------------------------------------------------
// sdefastsearch - A PHP Fast ESP Search API
// Copyright (C) 2008,2009 sueddeutsche.de GmbH, Hultschiner Str. 8, D-81677 München
// www.sueddeutsche.de.de - Andreas.Scheerer@sueddeutsche.de
//
// This file is part of sdefastsearch.
//
// sdefastsearch is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------

// includes
require_once dirname(__FILE__).'/../ISearchEngine.php';
require_once dirname(__FILE__).'/../SearchEngineException.php';
require_once dirname(__FILE__).'/../view/SearchView.php';
require_once dirname(__FILE__).'/../view/FieldType.php';
require_once dirname(__FILE__).'/../result/QueryResult.php';
require_once dirname(__FILE__).'/../result/DocumentSummary.php';
require_once dirname(__FILE__).'/../result/DocumentSummaryField.php';
require_once dirname(__FILE__).'/../result/Modifier.php';
require_once dirname(__FILE__).'/../result/Navigator.php';
require_once dirname(__FILE__).'/../result/NavigatorType.php';
require_once dirname(__FILE__).'/../result/QueryTransformation.php';
require_once dirname(__FILE__).'/../result/QueryTransformations.php';

/**
 * @package sde_fastsearch_http
 * @author  Andreas Scheerer <Andreas.Scheerer@sueddeutsche.de>
 * @version $Id: HttpSearchEngine.php 23 2010-02-19 17:08:12Z kenn.north $
 */
class HttpSearchEngine implements ISearchEngine {
    
    //--------------------------------------------------------------------------
    //---                         private fields                             ---
    //-------------------------------------------------------------------------- 
    private $_baseURL = "";
    private $_searchViewList = null;  
    private $_properties = array();
    
    //--------------------------------------------------------------------------
    //---                         constructor                                ---
    //-------------------------------------------------------------------------- 
    public function __construct(array $properties=array()) {
        $this->_properties = $properties;
    }
    
    //--------------------------------------------------------------------------
    //---                         public methods                             ---
    //-------------------------------------------------------------------------- 
    public function setServerAddress($hostport) {
        // TODO check base url and throw exception if invalid
        $this->_baseURL = 'http://'.$hostport.'/';
    }
    
    public function getSearchView($name='default', $reload=false) {
        // TODO add reload support 
        $searchViews = $this->getSearchViewList();
        if ($name!='default' && !in_array($name, $searchViews)) {
            throw new SearchEngineException("Searchview '$name' not available.");
        }
        
        $searchView = new SearchView($name);
        $searchView->initEngine($this);
        return $searchView;   
    }
    
    public function getSearchViewList() {
        if ($this->_searchViewList != null) {
            return $this->_searchViewList;
        }
        $url = $this->_baseURL."get?qrserverlist";

        // perform request
        if (isset($this->_properties['fastsearch.http.usecurl'])
            && $this->_properties['fastsearch.http.usecurl'] == true) {
			
            $resultArray = $this->getResultArray($url);
			//echo details($url);
			//echo details($resultArray);

            //$resultXML = @simplexml_load_string($resultString);
        } else {
            //$resultXML = @simplexml_load_file($url);
        }
        
        $views = array();
        if($resultXML) {
            foreach($resultXML->view as $view) {
                $views[] = (string)$view['name'];
            }
        }
        $this->_searchViewList = $views;
        return $this->_searchViewList;
    }

    public function setSearchViewList(array $viewList) {
        $this->_searchViewList = $viewList;
    }
    
    public function search(IQuery $query, array $fields, array $navigators) {
        
        // custom result view? 
        $resultView = 'search';
        try {
            $resultView = 
                $query->getParameterByType(BaseParameter::RESULT_VIEW)->getStringValue();
        } catch (NoSuchParameterException $e) {
            // no custom result view given 
        }
        
        // base search url
        $searchURL = $this->_baseURL."cgi-bin/xml-".$resultView."?";

        // custom search view?
        $searchView = 'default';
        try {
            $searchView = 
                $query->getParameterByType(BaseParameter::VIEW)->getStringValue();
            
            // append view parameter  
            // (note: must be first parameter cause other parameter dependencies)
            if ($searchView != 'default') {
                $searchURL .= "view=".rawurlencode($searchView);
            }
        } catch (NoSuchParameterException $e) {
            // no custom search view given 
        }
        
        // query string
        $queryStr = $query->getQueryString();
        
        // add filters to query string
        try {
            $filterStr = $query->getParameterByType(BaseParameter::FILTER);
            $queryStr = "and($queryStr, $filterStr)";
        } catch (NoSuchParameterException $e) {
            // no filter available
        }

        // append query parameter
        if (!empty($queryStr)) {
            if (substr($searchURL,-1) != '?') 
                $searchURL .= "&";    
            $searchURL .= "query=".rawurlencode($queryStr);
        }

        // Alveolar Click hack
        $searchURL = str_replace("%C7%82", "%01%C2", $searchURL);    
        
        // append other parameters
        $parameters = $query->getSearchParameters();


        foreach ($parameters as $parameter) {
            if ($parameter->getType() == BaseParameter::QUERY
                || $parameter->getType() == BaseParameter::FILTER
                || $parameter->getType() == BaseParameter::VIEW
                || $parameter->getType() == BaseParameter::RESULT_VIEW) 
                continue;
            if (substr($searchURL,-1) != '?') 
                $searchURL .= "&";    
            $searchURL .= $parameter->getName()
                ."=".rawurlencode($parameter->getStringValue());
        }
        
        // perform search
        //@TODO remove str_replace if scope search problem is solved
		$resultArray = $this->getResultArray($searchURL,$fields,$navigators);

        $documentSummaries = array();    
        $navigators = array();
        $queryTransformations = array();
        $segmentName = "";
        $totalHits   = 0;
        $maxRank     = 0;
        $timeUsed    = 0.0;
        
        $emptyResultSet = $resultArray['EMPTYRESULTSET']; 
        if ($emptyResultSet == null) {
            $segmentName = $resultArray['SEGMENT']['NAME'];
            $totalHits = (int)$resultArray['TOTALHITS'];
            $maxRank   = (int)$resultArray['MAXRANK'];
            $timeUsed  = (float)$resultArray['TIME'];

			$resultSet = $resultArray['HITS'];
            if (!empty($resultSet)) {
                foreach ($resultSet as $hit) {
                    $documentSummaryFields = array();
                    foreach ($hit as $key => $field) {
                        // TODO use correct field types. at the moment only type 'STRING' is used. 
                        $documentSummaryField = 
                            new DocumentSummaryField((string)$key,$field,
                                FieldType::STRING);
                        $documentSummaryFields[$key] = $documentSummaryField;                          
                    }
                    
                    // add standard fields
                    if (!array_key_exists('rank', $documentSummaryFields)) {
                        $documentSummaryFields['rank'] = 
                            new DocumentSummaryField('rank',(int)$hit['RANK'], FieldType::INTEGER);
                    }
                    if (!array_key_exists('siteid', $documentSummaryFields)) {
                        $documentSummaryFields['siteid'] = 
                            new DocumentSummaryField('siteid',(string)$hit['SITEID'], FieldType::STRING);
                    }
                    if (!array_key_exists('morehits', $documentSummaryFields)) {
                        $documentSummaryFields['morehits'] = 
                            new DocumentSummaryField('morehits',(int)$hit['MOREHITS'], FieldType::INTEGER);
                    }
                    if (!array_key_exists('fcocount', $documentSummaryFields)) {
                        $documentSummaryFields['fcocount'] = 
                            new DocumentSummaryField('fcocount',(int)$hit['FCOCOUNT'], FieldType::INTEGER);
                    }
                                            
                    // create and add final document summary
                    $documentSummary = new DocumentSummary($documentSummaryFields, 
                        (int)$hit['NO']);
                    $documentSummaries[] = $documentSummary;
                }
            }
                
            // add navigators to queryresult
            // TODO check if following also works if rpf:navigation is set to false             
            $navigation = $resultArray['NAVIGATION']['NAVIGATIONENTRY'];

			$numEntries = (int)$navigation['ENTRIES'];
            foreach ($navigation as $navigator) {
                $navName        = (string)$navigator['NAME'];
                $navDisplayname = (string)$navigator['DISPLAYNAME'];
                $navType        = (string)$navigator['TYPE'];
                $navUnit        = (string)$navigator['UNIT'];
                $navModifier    = (string)$navigator['MODIFIER'];
                $navScore       = (float)$navigator['SCORE'];
                $navSampleCount = (int)$navigator['SAMPLECOUNT'];
                $navUsedHits    = (int)$navigator['USEDHITS'];
                $navHitCount    = (int)$navigator['HITCOUNT'];
                $navRatio       = (float)$navigator['RATIO'];
                $navMin         = (float)$navigator['MIN'];
                $navMax         = (float)$navigator['MAX'];
                $navMean        = (float)$navigator['MEAN'];
                $navEntropy     = (float)$navigator['ENTROPY'];
                // TODO frequency error
                $navFrequencyError = 0;
                
                $modifiers = array();
				
                foreach ($navigator['NAVIGATIONELEMENTS'] as $navEntry) {
					
                    $modName     = (string)$navEntry['NAME'];
                    $modModifier = (string)$navEntry['MODIFIER'];
                    $modCount    = (int)$navEntry['COUNT'];
                    
                    if (ereg("^\[(.*);(.*)\]$", $modModifier)) {
                        $modIsInterval = true;
                    } else {
                        $modIsInterval = false;
                    }
                    //TODO calculate correct document ratio
                    $modifier = new Modifier($modName, $modCount, $modModifier, 
                        0.0, $navModifier, $modIsInterval);
                    $modifiers[] = $modifier;
                }
                
                // convert Types
                // TODO support Types different to String and DateTime
                $convNavType = 0;
                switch($navType) {
                    case 'DateTime':
                        $convNavType = NavigatorType::DATETIME;
                        break;
                    case 'String':
                    default:
                        $convNavType = NavigatorType::STRING;
                        break;
                    
                }
                $navigatorObj = new Navigator($navName, $navModifier, $navDisplayname,
                    $convNavType, $navUnit, $navScore, $navHitCount, $navUsedHits, 
                    $navRatio, $navSampleCount, $navMin, $navMax, $navMean, 
                    $navFrequencyError, $navEntropy, $modifiers);
                
                $navigators[] = $navigatorObj;                
            }
        }

        // query transformations
        $transforms = $resultArray['QUERYTRANSFORMS'];
        foreach ($transforms as $transform) {
		    $qtfName      = (string)$transform['NAME'];
            $qtfAction    = (string)$transform['ACTION'];
            $qtfQuery     = (string)$transform['QUERY'];
            $qtfCustom    = (string)$transform['CUSTOM'];
            $qtfMessage   = (string)$transform['MESSAGE'];
            $qtfMessageId = (int)$transform['MESSAGEID'];
            $queryTransformations[] = new QueryTransformation(
                $qtfName, $qtfAction, $qtfQuery, $qtfCustom, $qtfMessage, 
                $qtfMessageId);
        } 
        $queryResult = new QueryResult($query, new QueryTransformations($queryTransformations), 
            $documentSummaries, 
            $navigators, $segmentName, $totalHits, $maxRank, $timeUsed, 
            $searchURL);        
        return $queryResult;
    }
    
    //--------------------------------------------------------------------------
    //---                         private methods                            ---
    //-------------------------------------------------------------------------- 
    /**
     * @param string $url
     * @throws SearchEngineException
     *      
     */
    private function getResultArray($url, $fields=array('url','title','collection','body'), $navigators=array()) {
		$content = array();
		$xml = new XMLReader();
		$xml->open($url);
		$row = array();
		$navigator_elements = array();
		$querytransform_row = array();
		$hitinfo=array();
		$naventryinfo = array();
		while ($xml->read()) {
			if($xml->name == "ERROR" && $xml->nodeType == 1){
				$hasError=true;
				$error = "<br><br><b>Error: ".$xml->getAttribute("CODE")." - ";
				$xml->read();
				$error .= $xml->value."<br/></b>";
			}else if($xml->name == "RESULTSET" && $xml->nodeType == 1){
				$content['TOTALHITS']=$xml->getAttribute("TOTALHITS");
				$content['TIME']=$xml->getAttribute("TIME");
				$content['MAXRANK']=$xml->getAttribute("MAXRANK");		
			}else if($xml->name == "SEGMENT" && $xml->nodeType == 1){
				$content['SEGMENT']['NAME']=$xml->getAttribute("NAME");
			}else if($xml->name == "QUERYTRANSFORMS" && $xml->nodeType == 15){
				$content['QUERYTRANSFORMS'] = $querytransform_row;
				$querytransform_row = array();
			}else if ($xml->name =="QUERYTRANSFORM" && $xml->nodeType == 1){
				
				$querytransform_row[]=array("NAME"=>$xml->getAttribute("NAME"),"ACTION"=>$xml->getAttribute("ACTION"),"QUERY"=>$xml->getAttribute("QUERY"),"CUSTOM"=>$xml->getAttribute("CUSTOM"),"MESSAGE"=>$xml->getAttribute("MESSAGE"),"MESSAGEID"=>$xml->getAttribute("MESSAGEID"));
			}else if ($xml->name =="NAVIGATION" && $xml->nodeType == 1){
				
				$entries=$xml->getAttribute("ENTRIES");				
				$content['NAVIGATION']['ENTRIES'] = $entries;

			}else if($xml->name == "NAVIGATIONENTRY" && $xml->nodeType == 1 ){
				
				$name=$xml->getAttribute("NAME");
				if(empty($navigators) || in_array($name,$navigators)){
					$naventryinfo['NAME']=$xml->getAttribute("NAME");
					$naventryinfo['DISPLAYNAME']=$xml->getAttribute("DISPLAYNAME");
					$naventryinfo['TYPE']=$xml->getAttribute("TYPE");
					$naventryinfo['UNIT']=$xml->getAttribute("UNIT");
					$naventryinfo['MODIFIER']=$xml->getAttribute("MODIFIER");
					$naventryinfo['SCORE']=$xml->getAttribute("SCORE");
					$naventryinfo['SAMPLECOUNT']=$xml->getAttribute("SAMPLECOUNT");
					$naventryinfo['USEDHITS']=$xml->getAttribute("USEDHITS");
					$naventryinfo['HITCOUNT']=$xml->getAttribute("HITCOUNT");
					$naventryinfo['RATIO']=$xml->getAttribute("RATIO");
					$naventryinfo['MIN']=$xml->getAttribute("MIN");
					$naventryinfo['MAX']=$xml->getAttribute("MAX");
					$naventryinfo['MEAN']=$xml->getAttribute("MEAN");
					$naventryinfo['ENTROPY']=$xml->getAttribute("ENTROPY");
				}

			}else if($xml->name == "NAVIGATIONENTRY" && $xml->nodeType == 15 ){

				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['NAME'] = $naventryinfo['NAME'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['DISPLAYNAME'] = $naventryinfo['DISPLAYNAME'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['TYPE'] = $naventryinfo['TYPE'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['UNIT'] = $naventryinfo['UNIT'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['MODIFIER'] = $naventryinfo['MODIFIER'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['SCORE'] = $naventryinfo['SCORE'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['SAMPLECOUNT'] = $naventryinfo['SAMPLECOUNT'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['USEDHITS'] = $naventryinfo['USEDHITS'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['USEDHITS'] = $naventryinfo['HITCOUNT'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['RATIO'] = $naventryinfo['RATIO'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['MIN'] = $naventryinfo['MIN'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['MAX'] = $naventryinfo['MAX'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['MEAN'] = $naventryinfo['MEAN'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['ENTROPY'] = $naventryinfo['ENTROPY'];
				$content['NAVIGATION']['NAVIGATIONENTRY'][$naventryinfo['NAME']]['NAVIGATIONELEMENTS'] = $navigator_elements;
				$navigator_elements = array();

			}else if ($xml->name =="NAVIGATIONELEMENT" && $xml->nodeType == 1 ){

				$navigator_elements[]=array("NAME"=>$xml->getAttribute("NAME"),"MODIFIER"=>$xml->getAttribute("MODIFIER"),"COUNT"=>$xml->getAttribute("COUNT"));

			}else if($xml->name == "DOCUMENT" && $xml->nodeType == 1 && $ranking="on"){

				$position=array_keys($fields, "ranklog");
				$row[]=array("name"=>"ranklog","value"=>$xml->getAttribute("RANKLOG"),"position"=>$position[0]);

			}else if($xml->name == "HIT" && $xml->nodeType == 15){

				$final_row = array();
				$final_row = array_merge($final_row,$hitinfo);
				if(empty($fields)){
					$sorted_row = $row;
				}else{
					$sorted_row = array_value_sort($row, "position");
				}
				foreach($sorted_row as $sorted_cell){
					if($sorted_cell['name'] != ''){
						$final_row[$sorted_cell['name']] = $sorted_cell['value'];
					}
				}
				$content['HITS'][$hitinfo['NO']] = $final_row;
				$row = array();

			}else if ($xml->name == "HIT" && $xml->nodeType == 1) {
				
				$hitinfo['NO']=$xml->getAttribute("NO");
				$hitinfo['RANK']=$xml->getAttribute("RANK");
				$hitinfo['SITEID']=$xml->getAttribute("SITEID");
				$hitinfo['MOREHITS']=$xml->getAttribute("MOREHITS");
				$hitinfo['FCOCOUNT']=$xml->getAttribute("FCOCOUNT");

			}else if ($xml->name == "FIELD" && $xml->nodeType == 1) {
				$name=$xml->getAttribute("NAME");
				$find = array("<key>","</key>","<sep/>");
				$replace = array("<b>","</b>"," ... ");
				$innerXML = $xml->readInnerXML();
				$value = str_replace($find,$replace,$innerXML);
				if(empty($fields)){
					$row[]=array("name"=>$name,"value"=>$value);
				}else if(in_array($name,$fields)){
					$position=array_keys($fields, $name);
					$row[]=array("name"=>$name,"value"=>$value,"position"=>$position[0]);
				}
			}else if ($xml->name == "EMPTYRESULTSET" && $xml->nodeType == 1) {
				$content['EMPTYRESULTSET'] = true;
			}
		}
		if($content['NAVIGATION']['ENTRIES'] == "0"){
			$content['NAVIGATION']['NAVIGATIONENTRY'] = array();
		}
        return $content;
    }
}